#pragma once
#include <arm_neon.h>
#include <memory.h>
#include <stdio.h>
#include <new>
namespace open5g{
namespace ldpc{
namespace neon{

	const int maxR = 46;
	const int maxN = 19;
	const int maxC = 68;
	const int maxW = 361;
	const int maxZ = 384;
	const int maxS = 24;
	const int maxSlice = 24;
    typedef struct _G_Data {
        int8x16_t iLLR[maxW][maxS];
    } global_t;
    void dump(const char *name,int8x16_t a) {
        int8_t td[16];
        vst1q_s8(td,a);
        for(int i=0;i<16;i++) {
            printf("%3d,",(int)td[i]);
        }
        printf(" -- %s\n",name);
    };
    void dump(const char *name,uint8x16_t a) {
        uint8_t td[16];
        vst1q_u8(td,a);
        for(int i=0;i<16;i++) {
            printf("%3d,",(int)td[i]);
        }
        printf(" -- %s\n",name);
    };
class CNUSlice {
public:
    int8x16_t min0[maxR],min1[maxR],site[maxR],SignXorSum[maxR];
    int8x16_t llr,abs,inSite,sign,errorFlag;
    int8x16_t LLRBuffer[maxC];
    int8x16_t LLRBufferSign[maxC];
    global_t* G;
    int Slice;
    CNUSlice(int slice,global_t *g) {
        Slice = slice;
        G = g;
    };
    inline void toSABS(int8x16_t in, int Site, int Pos, int col) {
        llr = vqsubq_s8(in,G->iLLR[Pos][Slice]);
        llr = vqsubq_s8(llr,vdupq_n_s8(1));
        llr = vqaddq_s8(llr,vdupq_n_s8(1));
        LLRBuffer[col] = llr;
        abs  = vabsq_s8(llr);
        sign = vcgeq_s8(vdupq_n_s8(0),llr);
        LLRBufferSign[col] = sign;
        inSite = vdupq_n_s8((int8_t)Site);
    };
    inline void toSABSwithoutSub(int8x16_t in, int Site, int Pos, int col) {
        llr = in;
        llr = vqsubq_s8(llr,vdupq_n_s8(1));
        llr = vqaddq_s8(llr,vdupq_n_s8(1));
        LLRBuffer[col] = llr;
        abs  = vabsq_s8(llr);
        sign = vcgeq_s8(vdupq_n_s8(0),llr);
        LLRBufferSign[col] = sign;
        inSite = vdupq_n_s8((int8_t)Site);
    };
    inline void input0(int row) {
        min0[row] = abs;
        site[row] = inSite;
        SignXorSum[row] = sign;
    };
    inline void input1(int row) {
        // min0 = min(abs,min0)
        int8x16_t sel = vreinterpretq_s8_u8(vcgeq_s8(abs,min0[row]));
        site[row] = vbslq_s8(inSite,site[row],sel);
        // min1 = max(abs,min0)
        min1[row] = vbslq_s8(min0[row],abs,sel);
        min0[row] = vbslq_s8(abs,min0[row],sel);
        SignXorSum[row] = veorq_s8(SignXorSum[row],sign);
        // dump("abs",abs);
        // dump("min0",min0[row]);
        // dump("min1",min1[row]);
        // dump("site[row]",site[row]);
    };
    inline void input(int row) {
        // min0 = min(abs,min0)
        int8x16_t sel   = vreinterpretq_s8_u8(vcgeq_s8(abs,min0[row]));
        site[row]       = vbslq_s8(inSite,site[row],sel);
        int8x16_t gt    = vbslq_s8(min0[row],abs,sel);
        min0[row]       = vbslq_s8(abs,min0[row],sel);
        min1[row]       = vminq_s8(min1[row],gt);
        SignXorSum[row] = veorq_s8(SignXorSum[row],sign);
        // printf("\n");
        // dump("abs",abs);
        // dump("min0",min0[row]);
        // dump("min1",min1[row]);
        // dump("site[row]",site[row]);
    };
    inline void offset(int row, int offset, int limit) {
        min0[row] = vqsubq_s8(min0[row],vdupq_n_s8((int8_t)offset));
        min0[row] = vmaxq_s8(min0[row],vdupq_n_s8(0));
        min0[row] = vminq_s8(min0[row],vdupq_n_s8((int8_t)limit));
        min1[row] = vqsubq_s8(min1[row],vdupq_n_s8((int8_t)offset));
        min1[row] = vmaxq_s8(min1[row],vdupq_n_s8(0));
        min1[row] = vminq_s8(min1[row],vdupq_n_s8((int8_t)limit));
    };
    inline int8x16_t output(int Site, int Pos, int col, int row) {
        sign          = veorq_s8(SignXorSum[row],LLRBufferSign[col]);
        int8x16_t sel = vreinterpretq_s8_u8(vcgeq_s8(site[row]
        ,vdupq_n_s8((int8_t)Site)));
        int8x16_t min  = vbslq_s8(min0[row],min1[row],sel);
        int8x16_t illr = vbslq_s8(min,vqnegq_s8(min),sign);
        G->iLLR[Pos][Slice] = illr;
        int8x16_t llr = vqaddq_s8(illr,LLRBuffer[col]);
        // __m256i a = _mm256_adds_epi8(_mm256_abs_epi8(LLRBuffer[col]),siteConst[1]);
        // errorFlag = _mm256_or_si256(errorFlag,_mm256_and_si256(_mm256_cmpgt_epi8(min,a),SignXorSum[row]));
        // if(!check(SignXorSum[row])) {
        //   printf("output %d %d %d %d\n",Site,Pos,col,row);
        //   dump("llr ",llr);
        //   dump("sign ",sign);
        //   dump("min0[row]",min0[row]);
        //   dump("min1[row]",min1[row]);
        //   dump("SignXorSum[row]",SignXorSum[row]);
        //   dump("LLRBuffer[col]",LLRBuffer[col]);
        //   dump("illr",illr);
        //   dump("errorFlag",errorFlag);    
        // }
        return llr;
    };
    inline void clear() {
        errorFlag = vdupq_n_s8(0);
    };
    bool check(int8x16_t signxor) {
        int8_t t[16];
        vst1q_u8(t,signxor);
        long *buf = (long*)(&t);
        return ((buf[0] | buf[1]) == 0);
    };
}__attribute__((aligned(32))); // CNUSlice
class CNU {
  public:
    CNUSlice **cnuSlices;
    int8x16_t outBuf[maxS];
    int8x16_t afterShift[maxS];
    int8x16_t decOut[maxS*maxC];

    int Size;
    int Slices;
    int outCol;
    CNU(int size,global_t* g) {
        Size = size;
        Slices = (size+15)/16;
        cnuSlices = new CNUSlice*[maxSlice];
        for(int i=0;i<maxSlice;i++) {
        cnuSlices[i] = new((std::align_val_t) 32) CNUSlice(i,g);
        }
        outCol = 0;
    };
    ~CNU() {
      for(int i=0;i<maxSlice;i++) {
        delete cnuSlices[i];
      }
      delete cnuSlices;
    };
    void setSize(int size) {
        Size = size;
        Slices = (size+15)/16;
	};
    void prepared(int outSite, int Pos, int col, int row) {
        for(int i=0;i<Slices;i++) {
            outBuf[i] = cnuSlices[i]->output(outSite, Pos, col, row);
        }
        outCol = col;
    };

    void input(char *inLLR) {
        memcpy((void*)outBuf,inLLR,Size);
    };
    void shiftZ(int shift,int so) {
        memmove((char*)afterShift+shift,(char*)outBuf,Size-shift);
        memmove((char*)afterShift,(char*)outBuf+Size-shift,shift);
        char *outShift = (char*)decOut + Size * outCol;
        if(so!=0) {
            memmove(outShift+so,(char*)outBuf,Size-so);
            memmove(outShift,(char*)outBuf+Size-so,so);
        } else {
            memmove(outShift,(char*)outBuf,Size);  
        }
    };
    void done(int Pos, int inSite, int col, int row, int step) {
        for(int i=0;i<Slices;i++) {
            cnuSlices[i]->toSABS(afterShift[i],inSite,Pos,col);
        }
        switch(step) {
            case 0: {
                for(int i=0;i<Slices;i++) {
                    cnuSlices[i]->input0(row);
                }
                break;
            }
            case 1: {
                for(int i=0;i<Slices;i++) {
                    cnuSlices[i]->input1(row);
                }
            break; 
            }
            default: {
                for(int i=0;i<Slices;i++) {
                    cnuSlices[i]->input(row);
                }
                break;     
            }
        }
    };
    
    void doneWithoutPrepared(int Pos, int inSite, int col, int row, int step) {
        for(int i=0;i<Slices;i++) {
            cnuSlices[i]->toSABSwithoutSub(afterShift[i],inSite,Pos,col);
        }
        switch(step) {
            case 0: {
                for(int i=0;i<Slices;i++) {
                    cnuSlices[i]->input0(row);
                }
                break;
            }
            case 1: {
                for(int i=0;i<Slices;i++) {
                    cnuSlices[i]->input1(row);
                }
                break; 
            }
            default: {
                for(int i=0;i<Slices;i++) {
                    cnuSlices[i]->input(row);
                }
                break;     
            }
        }
    };
    void limit(int row, int offset, int limit) {
        for(int i=0;i<Slices;i++) cnuSlices[i]->offset(row,offset,limit);
    };
    void clear() {
    };
    bool stop() {
      int8x16_t errorFlag = vdupq_n_s8(0);
	  int8x16_t leftFlag  = vdupq_n_s8(0);
	  int len = Size/16;
	  int left = Size%16;
      for(int i=0;i<len;i++) {
        for(int r=0;r<4;r++)
          errorFlag = vorrq_s8(errorFlag,cnuSlices[i]->SignXorSum[r]);
      }
	  int ret = _mm256_movemask_epi8(errorFlag);
	  if(left!=0) {
        for(int r=0;r<4;r++)
          leftFlag = _mm256_or_si256(leftFlag,cnuSlices[len]->SignXorSum[r]);
	  	ret |= _mm256_movemask_epi8(leftFlag) & ((1<<left)-1);
	  }
	   
	  return (ret == 0);
    };
  }__attribute__((aligned(32))); // CNU
};
};
};